/* ir_builder_acc.js

  Copyright (C) 2007 by Lukas Loehrer
  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA

  Author: Lukas Loehrer <loehrerl [at] gmx [dot] net>
*/

const logger = module.require("package", "lib/filelogger").logger;

const util = module.require("package", "mozremote/util");
const dom_util = module.require("package", "dom_util");
const acc_util = module.require("package", "accessibility_util");

const Ci = Components.interfaces;
const nsIAccessible = Ci.nsIAccessible;
const nsIAccessibleTable = Ci.nsIAccessibleTable;
const nsIAccessibleRole = acc_util.nsIAccessibleRole;
const nsIAccessibleStates = acc_util.nsIAccessibleStates;

function Builder(aBrowserService) {
	this._browserService = aBrowserService;
}
Builder.prototype = {};

Builder.prototype._referenceNode = function (aNode) {
	/* Unfortunately, we cannot seem to reference nsIAccessible
	   remotely because we cannot add properties to it dynamically.
	   Thus, we have to use the DOM node instead. */
	return this._browserService.bless(acc_util.getDOMNodeFor(aNode));
};

Builder.prototype.visitDOMNode = function (aDOMNode) {
	var node = acc_util.getAccessibleFor(aDOMNode);
	return acc_util.visitNode(node, this);
};

Builder.prototype.defaultAction = function (aNode) {
	var result = {
		ref: this._referenceNode(aNode)
	};
	if (aNode.childCount > 0) {
		result.cn = acc_util.visitChildNodes(aNode, this);
	}
	return result;
};

Builder.prototype[nsIAccessibleRole.ROLE_WHITESPACE]  = function (aNode) {
	return null;
};

Builder.prototype[nsIAccessibleRole.ROLE_STATICTEXT]  = function (aNode) {
	return null;
};


Builder.prototype[nsIAccessibleRole.ROLE_TEXT_LEAF]  = function (aNode) {
	var text = aNode.name;

	var result = {
		prolog: text,
		ref: this._referenceNode(aNode)
	};
	return result;
};

Builder.prototype._definitionList = function (aNode) {
	var result = {
		role: "fm-role-list",
		ref: this._referenceNode(aNode),
		prolog: "Definition list",
		epilog: "Definition list end"
	};
	
	if (aNode.childCount > 0) {
		result.cn = acc_util.visitChildNodes(aNode, this);
		var DOMNode= acc_util.getDOMNodeFor(aNode);
				var count = 0;
		for (var node in util.ArrayIterator(DOMNode.childNodes)) {
			if (node.nodeType == Ci.nsIDOMNode.ELEMENT_NODE &&
				node.tagName.toLowerCase() == "dt") {
				count += 1;
			}
		}
		result.prolog += " with " + count + " items";
	}
	return result;
};

Builder.prototype._definitionTitle = function (aNode) {
	var result = {
		role: "fm-role-listitem",
		ref: this._referenceNode(aNode)
	};
	
	if (aNode.childCount > 0) {
		result.cn = acc_util.visitChildNodes(aNode, this);
	}
	return result;
};

Builder.prototype[nsIAccessibleRole.ROLE_PARAGRAPH]  = function (aNode) {
	var DOMNode = acc_util.getDOMNodeFor(aNode);
	var tagName = DOMNode.tagName.toLowerCase();
	if (tagName == "dl") {
		// Paragraph represents a definition list
		return this._definitionList(aNode);
	}
	if (tagName == "dt") {
		// This paragraph is a definition title
		return this._definitionTitle(aNode);
	}

	var text = "";

	var result = {
		prolog: text,
		ref: this._referenceNode(aNode)
	};
	if (aNode.childCount > 0) {
		result.cn = acc_util.visitChildNodes(aNode, this);
	}
	return result;
};

Builder.prototype[nsIAccessibleRole.ROLE_GRAPHIC]  = function (aNode) {
	var text = aNode.name;
	if (!text) {
		// do something clever here
		// i.e. check title, name or id attribute or use partes of the file name
		text = "Unlabelled";
	}

	var result = {
		prolog: text,
		ref: this._referenceNode(aNode),
		role: "fm-role-image"
	};
	return result;
};

Builder.prototype[nsIAccessibleRole.ROLE_LINK]  = function (aNode) {
	// Do not treat mere named anchors as links
	if (!acc_util.checkStateFlag(aNode, "LINKED")) {
		return this.defaultAction(aNode);
	}

	var DOMNode = acc_util.getDOMNodeFor(aNode);
	var result = {
		ref: this._referenceNode(aNode),
		role: "fm-role-link",
		props: {
			"fm-url": DOMNode.href
		}
	};
	if (acc_util.checkStateFlag(aNode, "TRAVERSED")) {
		result.props["fm-link-visited"] = true;
	}
	if (aNode.childCount > 0) {
		result.cn = acc_util.visitChildNodes(aNode, this);
	}
	return result;
}

Builder.prototype[nsIAccessibleRole.ROLE_LISTITEM]  = function (aNode) {
	var result = {
		ref: this._referenceNode(aNode),
		role: "fm-role-listitem"
	};
	if (aNode.childCount > 0) {
		result.cn = acc_util.visitChildNodes(aNode, this);
	}
	return result;
}
Builder.prototype[nsIAccessibleRole.ROLE_HEADING]  = function (aNode) {
	var DOMNode = acc_util.getDOMNodeFor(aNode);
	var result = {
		ref: this._referenceNode(aNode),
		role: "fm-role-heading",
		props: {
			"fm-heading-level": parseInt(DOMNode.nodeName.charAt(1))
		}
	};
	if (aNode.childCount > 0) {
		result.cn = acc_util.visitChildNodes(aNode, this);
	}
	return result;
}

Builder.prototype[nsIAccessibleRole.ROLE_PANE]  = function (aNode) {
	var DOMNode = acc_util.getDOMNodeFor(aNode);
	var DOMDocument = DOMNode.QueryInterface(Ci.nsIDOMHTMLDocument);
	var result = {
		ref: this._referenceNode(aNode),
		role: "fm-role-frame",
		prolog: "Frame start.",
		epilog: "Frame end.",
		props: {
			"fm-title": DOMDocument.title,
			"fm-url": DOMDocument.URL
		}
	};
	if (aNode.childCount > 0) {
		result.cn = acc_util.visitChildNodes(aNode, this);
	}
	return result;
};

Builder.prototype[nsIAccessibleRole.ROLE_DOCUMENT]  = function (aNode) {
	var accDoc = aNode.QueryInterface(Ci.nsIAccessibleDocument);
	var result = {
		ref: this._referenceNode(aNode),
		role: "fm-role-document",
		prolog: aNode.name,
		props: {
			"fm-title": accDoc.title,
			"fm-url": accDoc.URL
		}
	};
	if (aNode.childCount > 0) {
		result.cn = acc_util.visitChildNodes(aNode, this);
	}
	return result;
};

// Ignore embed elements
Builder.prototype[nsIAccessibleRole.ROLE_TEXT_CONTAINER]  = function (aNode) {
	var DOMNode = acc_util.getDOMNodeFor(aNode);

	if (DOMNode instanceof Ci.nsIDOMHTMLEmbedElement) {
		return null;
	}
	return this.defaultAction(aNode);
};

// {{{Form controls

Builder.prototype[nsIAccessibleRole.ROLE_ENTRY]  = function (aNode) {
	try {
		var DOMNode = acc_util.getDOMNodeFor(aNode);

		var value, text, role;
		role = "fm-role-entry";
		value = DOMNode.value;
		// Buffer text is basically value but at least 2 characters long
		text = value;
		for (var i = 0; i < (2-value.length); ++i) {
			text +=" ";
		}
	
		var result = {
			ref: this._referenceNode(aNode),
			role: role,
			prolog: text,
			value: value,
			label: aNode.name
		};
		return result;
	} catch (e) {
		logger.error("Error: " + e, e);
	}
}	;

Builder.prototype[nsIAccessibleRole.ROLE_PASSWORD_TEXT]  = function (aNode) {
	try {
	var DOMNode = acc_util.getDOMNodeFor(aNode);
	var isPassword = acc_util.checkStateFlag(aNode, "PROTECTED");
	var value, text, role;
	if (isPassword) {
		role = "fm-role-password";

		value = "";
		var n = DOMNode.value.length;
		for (var i = 0; i < n; ++i) {
			value += "*";
		}
	} else {
		role = "fm-role-entry";
		value = DOMNode.value;
	}
		// Buffer text is basically value but at least 2 characters long
		text = value;
		for (var i = 0; i < (2-value.length); ++i) {
			text +=" ";
		}
	
	var result = {
		ref: this._referenceNode(aNode),
		role: role,
		prolog: text,
		value: value,
		label: aNode.name
	};
	return result;
	} catch (e) {
		logger.error("Error: " + e, e);
	}
}	;


Builder.prototype[nsIAccessibleRole.ROLE_PUSHBUTTON]  = function (aNode) {
	var text = aNode.name;
	if(!text || text.length == 0) {
		// We can certainly do better than this...
		text = "Unlabelled button";
	}
	var result = {
		prolog: text,
		ref: this._referenceNode(aNode),
		role: "fm-role-pushbutton",
		label: aNode.name
	};
	return result;
};

Builder.prototype[nsIAccessibleRole.ROLE_COMBOBOX]  = function (aNode) {
	var DOMNode = acc_util.getDOMNodeFor(aNode);
	var text = aNode.value;
	if (!text || text.length == 0) {
		text = "*empty selection*";
	}
	
	var result = {
		ref: this._referenceNode(aNode),
		role: "fm-role-combobox",
		prolog: text,
		label: aNode.name,
		value: aNode.value,
		state: ((DOMNode.selectedIndex + 1) + " of " + DOMNode.options.length)
	};
	return result;
};

Builder.prototype[nsIAccessibleRole.ROLE_CHECKBUTTON]  = function (aNode) {
	var result = {
		ref: this._referenceNode(aNode),
		role: "fm-role-checkbox",
		prolog: "  ", // Two spaces
		state: ((acc_util.checkStateFlag(aNode, "CHECKED")) ? "checked" : "not checked"),
		label: aNode.name
	};
	return result
};

Builder.prototype[nsIAccessibleRole.ROLE_RADIOBUTTON]  = function (aNode) {
	var DOMNode = acc_util.getDOMNodeFor(aNode);
 	var selectionInfo = "";
	if (DOMNode.form) {
		var radioGroup = DOMNode.form[DOMNode.name];
		var index = null;
		for (var i = 0; i < radioGroup.length; ++i) {
			if (radioGroup[i] == DOMNode) {
				index = i;
				break;
			}
		}
		if (index != null) {
			selectionInfo = " " + (index + 1) + " of " + radioGroup.length;
		}
	}
	var label = (aNode.name) ? aNode.name : "";
	var result = {
		ref: this._referenceNode(aNode),
		role: "fm-role-radiobutton",
		prolog: "  ", // Two spaces,
		state: ((DOMNode.checked) ? "selected" : "not selected"),
		label: label + selectionInfo
	};
	return result;
};

// }}}

Builder.prototype[nsIAccessibleRole.ROLE_CELL] = function (aNode){
	var result = this.defaultAction(aNode);
	result.role = "fm-role-cell";
	return result;
}

Builder.prototype[nsIAccessibleRole.ROLE_TABLE] = function (aNode){
	var result = {
		ref: this._referenceNode(aNode)
	};
	if (aNode.childCount > 0) {
		result.cn = acc_util.visitChildNodes(aNode, this);
	}
	try {
		var accTable = aNode.QueryInterface(nsIAccessibleTable);
		if (!(accTable.isProbablyForLayout())) {
			result.prolog = "Table with " + accTable.rows + " rows and " + accTable.columns + " columns";
			result.epilog = "Table end";
			var level = acc_util.getNestingLevel(accTable);
			if (level > 0) {
				result.prolog += " nesting level " + level;
				result.epilog += " nesting level " + level;
			}
			result.role = "fm-role-table";
			if (accTable.summary) {
				result.prolog += "\n" + accTable.summary;
			}
		}
	} catch (e) {
		logger.error("ir_builder_acc: table: " + e, e);
	}
	return result;
};

Builder.prototype[nsIAccessibleRole.ROLE_LIST] = function (aNode){
	var result = {
		ref: this._referenceNode(aNode),
		role: "fm-role-list"
	};
	if (aNode.childCount > 0) {
		result.cn = acc_util.visitChildNodes(aNode, this);
	}
	result.prolog = "List with " + aNode.childCount + " items";
	result.epilog = "List end";
	var level = acc_util.getNestingLevel(aNode);
	if (level > 0) {
		result.prolog += " nesting level " + level;
		result.epilog += " nesting level " + level;
	}
	return result;
};

// {{{ Emacs local variables

// local variables:
// folded-file: t
// end:

// }}}
